#pragma once
#include "index.hpp"
#include "util.hpp"
#include <algorithm>
#include <unordered_map>
#include <jsoncpp/json/json.h>
#include "log.hpp"
namespace ns_searcher
{

struct InvertedElemPrint
{ 
    uint64_t doc_id;//文档id
    int weight;//累加权重
    std::vector<std::string> words;//一个doc_id对应多个关键字
    InvertedElemPrint():doc_id(0), weight(0)
    {}
};
class Searcher
{
private:
    ns_index::Index *index; //供系统进行查找的索引
public:
    Searcher(){}
    ~Searcher(){}
    //建立索引需要传入的是经过数据清洗之后的文件存放的位置 input其实就是:data/raw_html/raw.txt
    void InitSearcher(const std::string &input) //input:去标签之后的数据的位置
    {
        //1. 获取或者创建index对象
        index = ns_index::Index::GetInstance();
        //std::cout << "获取index单例成功..." << std::endl;
        LOG(NORMAL,"获取index单例成功");
        //2. 根据index对象建立索引
        index->BuildIndex(input);
        //std::cout << "建立正排和倒排索引成功..." << std::endl;
        LOG(NORMAL,"建立正排和倒排索引成功...");
    }
    

    //获取一部分摘要
    std::string GetDesc(const std::string &html_content, const std::string &word)
    {       
        //1.在html_content范围内,找word首次出现的位置
        auto cmp = [](int x, int y){ return (std::tolower(x) == std::tolower(y)); };
        auto iter =  std::search(html_content.begin(), html_content.end(), word.begin(), word.end(), cmp);

        if(iter == html_content.end())
        { 
            return "None1";
        }
        int pos = iter - html_content.begin();

        //2. 获取start,end的位置  
        int start = 0; //默认就是在文本的起始位置
        int end = html_content.size() - 1;//默认是文本的结尾
        //找到word在html_content中的首次出现的位置,然后往前找50字节,如果没有,就从begin开始
        //往后找100字节如果没有,到end就可以的 ,然后截取出这部分内容
        const int prev_step = 50;
        const int next_step = 100;
        if(pos - prev_step > 0) 
            start = pos - prev_step;
        if(pos+next_step < end) 
            end = pos + next_step;

        //3. 截取子串,然后return
        if(start >= end) 
            return "None2"; 
        //从start开始,截取end - start个字节的内容
        std::string desc = html_content.substr(start, end - start);
        desc += "...";
        return desc;
    }

    //query:用户搜索关键字  json_string: 返回给用户浏览器的搜索结果
    void Search(const std::string &query, std::string *json_string)
    {
        //1.对query进行按照searcher的要求进行分词
        std::vector<std::string> words;//放的是分词之后的结果
        ns_util::JiebaUtil::CutString(query, &words);

        //2.就是根据分词的各个"词"，进行index索引查找
        std::vector<InvertedElemPrint> inverted_list_all;//存放所有经过去重之后的倒排拉链
        //根据doc_id进行去重,凡是id相同的倒排索引节点,其关键字都放在InvertedElemPrint的vector里面
        std::unordered_map<uint64_t, InvertedElemPrint> tokens_map;

        //因为我们建立索引的时候,建立index是忽略大小写，所以搜索的时候关键字也需要忽略大小写
        for(std::string word : words)
        {  
            boost::to_lower(word);//将切分之后的查找关键字转为小写
            //先查倒排
            ns_index::InvertedList *inverted_list = index->GetInvertedList(word);//通过关键字获得倒排拉链
            if(nullptr == inverted_list)//该关键字不存在倒排拉链
            {
                continue;//检测下一个关键词
            }
            for(const auto &elem : *inverted_list) //遍历倒排拉链,合并索引节点
            {
                //[]:如果存在直接获取，如果不存在新建
                auto &item = tokens_map[elem.doc_id]; //根据文档id获得其对应的关键字集合
                //此时item一定是doc_id相同的倒排拉链打印节点
                item.doc_id = elem.doc_id;
                item.weight += elem.weight;//权值累加
                item.words.push_back(elem.word);  //把当前的关键字放到去重的倒排拉链打印节点当中的vector当中保存
            }
        }
        
        for(const auto &item : tokens_map)//遍历去重之后的map
        { 
            inverted_list_all.push_back(std::move(item.second)); //插入倒排拉链打印节点到inverted_list_all
        }

        //3.汇总查找结果，按照相关性(weight)进行降序排序
        auto cmp = [](const InvertedElemPrint &e1, const InvertedElemPrint &e2){
            return e1.weight > e2.weight;
        };
        std::sort(inverted_list_all.begin(), inverted_list_all.end(),cmp);
        
        //4.根据查找出来的结果，构建json串 -- jsoncpp --通过jsoncpp完成序列化&&反序列化
        Json::Value root;
        for(auto &item : inverted_list_all){ 
            //item就是倒排索引打印节点
            ns_index::DocInfo * doc = index->GetForwardIndex(item.doc_id);//根据文档id->查询正排,返回正排索引节点
            if(nullptr == doc){
                continue;
            }
            //doc里面就有title,url,content,文档id
            //我们想给浏览器返回的是网页的title,内容摘要,链接
            Json::Value elem;
            elem["title"] = doc->title;
            //item是经过去重之后的节点, 此时的words是个vector,直接使用words[0]获得摘要即可,0号下标的值一定存在
            elem["desc"] = GetDesc(doc->content, item.words[0]); 
            elem["url"]  = doc->url;

            //for deubg, for delete
            elem["id"] = (int)item.doc_id; //doc_id是uint64 ,json可能报错,所以转为int
            elem["weight"] = item.weight; //doc_id,weight虽然是int,但是json会帮我们自动转为string 
            root.append(elem);
        }

        //Json::StyledWriter writer; //为了方便调试观看
        Json::FastWriter writer;
        *json_string = writer.write(root);
    }
    


};
}